package me.zhenqu.redis.utils;


import com.esotericsoftware.reflectasm.MethodAccess;
import org.joda.time.DateTime;
import java.lang.reflect.Method;
import java.util.*;
public class ReflectUtils {
    private static final Map<Class<?>, MethodAccess> cachedMethodAccess = new Hashtable<Class<?>, MethodAccess>();

    public static void setProperty(Object vo, String method, Object arg) {
        Class<?> clz = vo.getClass();
        setProperty(clz, vo, "set" + StringUtils.capitalize(method), arg);
    }

    public static void setProperty(Class<?> clz, Object vo, String method, Object arg) {
        if (clz == null && vo != null) clz = vo.getClass();
        MethodAccess methodAccess = getMethodAccess(clz);
        Method[] methods = methodAccess.getMethods();
        int index = methodAccess.getIndex(method);
        if (arg != null) {
            Method m = methods[index];
            Class<?>[] parameterTypes = m.getParameterTypes();
            if (parameterTypes.length != 1) throw new IllegalArgumentException();
            Class<?> parameterType = parameterTypes[0];
            Class<?> real = arg.getClass();
            if (!parameterType.equals(real)) {
                if (parameterType.equals(Integer.TYPE) ||
                        Integer.class.equals(parameterType)) {
                    if (Number.class.isAssignableFrom(real)) {
                        arg = ((Number) arg).intValue();
                    } else if (String.class.equals(real)) {
                        arg = Integer.parseInt((String) arg);
                    } else {
                        throw new IllegalArgumentException();
                    }
                } else if (parameterType.equals(Long.TYPE) ||
                        Long.class.equals(parameterType)) {
                    if (Number.class.isAssignableFrom(real)) {
                        arg = ((Number) arg).longValue();
                    } else if (String.class.equals(real)) {
                        arg = Long.parseLong((String) arg);
                    } else if (Date.class.isAssignableFrom(real)) {
                        arg = new Date((Long) arg);
                    } else if (DateTime.class.isAssignableFrom(real)) {
                        arg = new DateTime((Long) arg);
                    } else {
                        throw new IllegalArgumentException();
                    }
                } else if (Date.class.isAssignableFrom(parameterType)) {
                    arg = new Date((Long) arg);
                } else if (DateTime.class.isAssignableFrom(parameterType)) {
                    arg = new DateTime(arg);
                }
            }
        }
        methodAccess.invoke(vo, index, arg);
    }

    public static Object invoke(Class<?> clz, Object vo, String method, Object... param) {
        if (clz == null && vo != null) clz = vo.getClass();
        MethodAccess methodAccess = getMethodAccess(clz);
        return methodAccess.invoke(vo, method, param);
    }

    public static Object invoke(Class<?> clz, Object vo, String method, byte[] param) {
        if (clz == null && vo != null) clz = vo.getClass();
        MethodAccess methodAccess = getMethodAccess(clz);
        return methodAccess.invoke(vo, method, param);
    }

    private static MethodAccess getMethodAccess(Class<?> clz) {
        MethodAccess methodAccess = cachedMethodAccess.get(clz);
        if (methodAccess == null) {
            synchronized (cachedMethodAccess) {
                methodAccess = MethodAccess.get(clz);
                cachedMethodAccess.put(clz, methodAccess);
            }
        }
        return methodAccess;
    }

    public static Map<String, Class<?>> getTypes(Class<?> clazz) {
        Map<String, Class<?>> result = new HashMap<String, Class<?>>();
        MethodAccess methodAccess = getMethodAccess(clazz);
        Method[] methods = methodAccess.getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (!Void.TYPE.equals(method.getReturnType()) && method.getParameterTypes().length == 0) {
                if (name.startsWith("get") && name.length() > 3) {
                    name = Character.toLowerCase(name.charAt(3)) + (name.length() > 4 ? name.substring(4) : "");
                } else if (name.startsWith("is") && name.length() > 2) {
                    name = Character.toLowerCase(name.charAt(2)) + (name.length() > 3 ? name.substring(3) : "");
                } else {
                    continue;
                }
                result.put(name, method.getReturnType());
            }
        }
        return result;
    }

    public static Map<String, Object> describe(Object bean) {
        if (bean == null) return Collections.emptyMap();
        Map<String, Object> result = new HashMap<String, Object>();
        MethodAccess methodAccess = getMethodAccess(bean.getClass());
        Method[] methods = methodAccess.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            String name = method.getName();
            if (!Void.TYPE.equals(method.getReturnType()) && method.getParameterTypes().length == 0) {
                if (name.startsWith("get") && name.length() > 3) {
                    name = Character.toLowerCase(name.charAt(3)) + (name.length() > 4 ? name.substring(4) : "");
                } else if (name.startsWith("is") && name.length() > 2) {
                    name = Character.toLowerCase(name.charAt(2)) + (name.length() > 3 ? name.substring(3) : "");
                } else {
                    continue;
                }
                Object ret = methodAccess.invoke(bean, i);
                result.put(name, ret);
            }
        }
        return result;
    }

    public static Object getProperty(Object o, String name) {
        Class<?> clz = o.getClass();
        MethodAccess methodAccess = getMethodAccess(clz);
        Object ret;
        try {
            ret = methodAccess.invoke(o, "get" + StringUtils.capitalize(name));
        } catch (Exception e) {
            ret = methodAccess.invoke(o, "is" + StringUtils.capitalize(name));
        }

        return ret;
    }
}

